package com.vict.framework.pay.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.vict.framework.bean.UserContext;
import com.vict.framework.pay.bean.*;
import com.vict.framework.pay.entity.PayBack;
import com.vict.framework.pay.entity.PayOrder;
import com.vict.framework.pay.mapper.PayBackMapper;
import com.vict.framework.pay.mapper.PayOrderMapper;
import com.vict.framework.pay.taskimpl.PayCallTask;
import com.vict.framework.settimeout.annotation.SetTimeout;
import com.vict.framework.settimeout.dto.SetTimeoutDto;
import com.vict.framework.task.util.TaskExecuteUtil;
import com.vict.framework.utils.UserContextUtil;
import com.vict.framework.utils.lock.LockApi;
import com.vict.utils.SpringUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Slf4j
@Component
public class FramworkPayInnerService {

    @Autowired
    private FramworkPayInnerService framworkPayInnerService;

    @Autowired
    private PayOrderMapper payOrderMapper;

    @Autowired
    private PayBackMapper payBackMapper;

    public void pay(PayInnerDto payInnerDto) {
        PayAwaitDTO payAwaitDTO = getPayAwaitDTO(payInnerDto);

        String payDtoId = payAwaitDTO.getPayDtoId();
        LockApi lock = LockApi.getLockOverThrowException("payServer_payDtoId-" + payDtoId, 40L);
        try{
            lock.lockBlock();
            framworkPayInnerService.payTrans(payInnerDto, payAwaitDTO);
        }finally{
            lock.unlockIfSuccess();
        }
    }

    public static PayAwaitDTO getPayAwaitDTO(PayInnerDto payInnerDto){
        List<PayArgsDto> args = payInnerDto.getArgs();
        for(PayArgsDto arg : args){
            String className = arg.getClassName();
            if(className.equals("com.vict.framework.pay.bean.PayAwaitDTO")){
                Object obj = arg.getObj();
                JSON o = (JSON)JSONObject.toJSON(obj);
                PayAwaitDTO payAwaitDTO = JSONObject.toJavaObject(o, PayAwaitDTO.class);
                return payAwaitDTO;
            }
        }
        throw new RuntimeException("支付异常,无payAwaitDTO");
    }

    @Transactional
    public void payTrans(PayInnerDto payInnerDto, PayAwaitDTO payAwaitDTO) {
        long now = System.currentTimeMillis();
        // 检查是否已经创建
        String payDtoId = payAwaitDTO.getPayDtoId();
        PayOrder payOrderExist = payOrderMapper.selectById(payDtoId);
        if(payOrderExist != null){
            throw new RuntimeException("支付异常,订单已存在");
        }


        // 创建待支付订单
        PayOrder payOrder = new PayOrder();


        payOrder.setPayDtoId(payAwaitDTO.getPayDtoId());
        payOrder.setMoney(payAwaitDTO.getMoney());
        payOrder.setPayStatus(PayOrder.PayStatus.payStatus_wait);
        payOrder.setStartTime(new Timestamp(now));
        payOrder.setOverTime(new Timestamp(now + payAwaitDTO.getOverTimeSecond() * 1000L));
        payOrder.setClassName(payInnerDto.getClassName());
        payOrder.setMethodName(payInnerDto.getMethodName());
        payOrder.setUserContext(
                Optional.ofNullable(payInnerDto).map(o-> o.getUserContext()).map(o-> JSONObject.toJSONString(o))
                        .map(o-> JSONObject.parseObject(o))
                        .orElse(null)
        );
        JSONArray args = Optional.ofNullable(payInnerDto)
                .map(o -> o.getArgs())
                .map(o -> JSONObject.toJSONString(o))
                .map(o -> JSONArray.parseArray(o))
                .orElse(null);
        payOrder.setArgs(args);
        payOrder.setPayRemark(payAwaitDTO.getPayRemark());


        payOrderMapper.insert(payOrder);

        SetTimeoutDto setTimeoutDto = new SetTimeoutDto();
        setTimeoutDto.setTime(new Timestamp(now + payAwaitDTO.getOverTimeSecond() * 1000L));

        // 超时
        framworkPayInnerService.payOverTime(setTimeoutDto, payAwaitDTO.getPayDtoId());
    }

    @SetTimeout
    public void payOverTime(SetTimeoutDto setTimeoutDto, String payDtoId) {

        LockApi lock = LockApi.getLockOverThrowException("payServer_payDtoId-" + payDtoId, 40L);
        try{
            lock.lockBlock();

            PayOrder payOrder = payOrderMapper.selectById(payDtoId);
            payOrder.setPayStatus(PayOrder.PayStatus.payStatus_cancel);
            payOrder.setCancelTime(new Timestamp(System.currentTimeMillis()));
            payOrderMapper.updateById(payOrder);

        }finally{
            lock.unlockIfSuccess();
        }

    }


    /**
     *
     * @param payDtoId
     * @return 退款明细no
     */
    public void payBack(String payDtoId, String payBackDtoId, BigDecimal backMoney) {

        LockApi lock = LockApi.getLockOverThrowException("payServer_payDtoId-" + payDtoId, 40L);
        try{
            lock.lockBlock();
            framworkPayInnerService.payBackTrans(payDtoId, payBackDtoId, backMoney);
        }finally{
            lock.unlockIfSuccess();
        }
    }

    @Transactional
    public void payBackTrans(String payDtoId, String payBackDtoId, BigDecimal backMoney){
        long now = System.currentTimeMillis();

        PayOrder payOrder = payOrderMapper.selectById(payDtoId);

        if(payOrder == null){
            throw new RuntimeException("不存在支付单");
        }

        List<PayBack> payBacks = payBackMapper.selectList(
                new LambdaQueryWrapper<PayBack>().eq(PayBack::getPayDtoId, payDtoId)
        );
        payBacks = Optional.ofNullable(payBacks).orElse(new ArrayList<>());

        BigDecimal alreadyBackMoney = payBacks.stream().map(o -> o.getBackMoney()).reduce(BigDecimal.ZERO, BigDecimal::add);

        BigDecimal lessMoney = payOrder.getMoney().subtract(alreadyBackMoney);

        if (backMoney.compareTo(lessMoney) > 0){
            throw new RuntimeException("退款金额大于支付金额");
        }

        if(payOrder.getIsBack() == null || payOrder.getIsBack() == 0){
            payOrder.setIsBack(1);
            payOrderMapper.updateById(payOrder);
        }

        PayBack payBack = new PayBack();
        payBack.setPayBackDtoId(payBackDtoId);
        payBack.setPayDtoId(payDtoId);
        payBack.setStartTime(new Timestamp(now));
        payBack.setBackMoney(backMoney);
        payBack.setBackTime(new Timestamp(now));
        payBackMapper.insert(payBack);
    }

    public void payCancel(String payDtoId) {
        PayOrder payOrder = payOrderMapper.selectById(payDtoId);

        LockApi lock = LockApi.getLockOverThrowException("payServer_payDtoId-" + payDtoId, 40L);
        try{
            lock.lockBlock();

            if(payOrder.getPayStatus() == PayOrder.PayStatus.payStatus_wait){
                // 可以取消
            }else{
                throw new RuntimeException("该订单不能取消");
            }

            payOrder.setPayStatus(PayOrder.PayStatus.payStatus_cancel);
            payOrder.setCancelTime(new Timestamp(System.currentTimeMillis()));

            payOrderMapper.updateById(payOrder);
        }finally{
            lock.unlockIfSuccess();
        }


    }

    @Transactional
    public void payFinish(PayTaskDTO payTaskDTO) {
        PayOrder payOrder = payOrderMapper.selectById(payTaskDTO.getPayDtoId());
        if(payOrder.getPayStatus() == PayOrder.PayStatus.payStatus_wait){
            // 正常 可以完成支付
        }else{
            return;
        }

        payOrder.setPayType(payTaskDTO.getPayType());
        payOrder.setPayStatus(PayOrder.PayStatus.payStatus_success);
        payOrder.setSuccessTime(payTaskDTO.getSuccessTime());

        payOrderMapper.updateById(payOrder);

        TaskExecuteUtil.sendTask(PayCallTask.type, payTaskDTO.getPayDtoId(), new Timestamp(System.currentTimeMillis()));
    }

    @SneakyThrows
    public void payCall(String payDtoId) {
        PayOrder payOrder = payOrderMapper.selectById(payDtoId);

        UserContext userContext = Optional.ofNullable(payOrder).map(o -> o.getUserContext())
                .map(o -> o.toJavaObject(UserContext.class))
                .orElse(null);

        UserContextUtil.setContext(userContext);

        String className = payOrder.getClassName();
        String methodName = payOrder.getMethodName();

        List<PayArgsDto> list = Optional.ofNullable(payOrder).map(o -> o.getArgs())
                .map(o -> o.toJavaList(PayArgsDto.class))
                .orElse(null);


        Object springBeanService = SpringUtils.getBean(Class.forName(className));
        Method method = Arrays.stream(springBeanService.getClass().getMethods()).filter(o -> o.getName().equals(methodName)).collect(Collectors.toList()).get(0);

        Object[] args = new Object[list.size()];
        for(int i = 0 ; i < list.size() ; i++){
            PayArgsDto payArgsDto = list.get(i);
            String paramClassName = payArgsDto.getClassName();
            Object obj = payArgsDto.getObj();

            try{
                Class<?> aClass = Class.forName(paramClassName);
                Object o1 = JSONObject.toJavaObject((JSON) JSONObject.toJSON(obj), aClass);
                args[i] = o1;
            }catch(Exception e){
                log.error("序列化异常 为常量json数据");
                args[i] = obj;
            }
        }

        PayAwaitDTO payAwaitDTO = null;
        for(Object arg : args){
            if(arg instanceof PayAwaitDTO){
                payAwaitDTO = (PayAwaitDTO)arg;
                break;
            }
        }
        if(payAwaitDTO == null){
            throw new RuntimeException("PayAwait注释的方法 必须有PayDTO参数");
        }

        try{
            payAwaitDTO.setInvokeFlag(true);

            method.invoke(springBeanService, args);

            return;
        }catch(Exception e){
            log.error("支付回调异常", e);
            throw new RuntimeException("支付回调异常");
        }
    }
}
